package tech

import (
	"fmt"
	"slices"
	"ssp/set"
	"strings"
)

type ChainNode struct {
	pre  *ChainNode
	item GroupItem
}

type GroupItem struct {
	cand  int
	group [9]*Cell
}

func (i GroupItem) getCells() []*Cell {
	cells := make([]*Cell, 0, 9)
	for _, cell := range i.group {
		if cell == nil {
			break
		}
		cells = append(cells, cell)
	}
	return cells
}

func (i GroupItem) String() string {
	if i.group[1] == nil {
		return fmt.Sprintf("R%vC%v[%v]", i.group[0].rowNum+1, i.group[0].colNum+1, toValue(i.cand))
	}
	sameRow := true
	sameCol := true
	sameBox := true
	for _, cell := range i.group {
		if cell == nil {
			break
		}
		if cell.rowNum != i.group[0].rowNum {
			sameRow = false
		}
		if cell.colNum != i.group[0].colNum {
			sameCol = false
		}
		if cell.boxNum != i.group[0].boxNum {
			sameBox = false
		}
	}
	var b strings.Builder
	if sameRow {
		fmt.Fprintf(&b, "R%vC", i.group[0].rowNum+1)
		for _, cell := range i.group {
			if cell == nil {
				break
			}
			fmt.Fprintf(&b, "%v", cell.colNum+1)
		}
		fmt.Fprintf(&b, "[%v]", toValue(i.cand))
		return b.String()
	} else if sameCol {
		fmt.Fprint(&b, "R")
		for _, cell := range i.group {
			if cell == nil {
				break
			}
			fmt.Fprintf(&b, "%v", cell.rowNum+1)
		}
		fmt.Fprintf(&b, "C%v[%v]", i.group[0].colNum+1, toValue(i.cand))
		return b.String()
	} else if sameBox {
		fmt.Fprintf(&b, "B%vP", i.group[0].boxNum+1)
		for _, cell := range i.group {
			if cell == nil {
				break
			}
			fmt.Fprintf(&b, "%v", cell.cellNum+1)
		}
		fmt.Fprintf(&b, "[%v]", toValue(i.cand))
		return b.String()
	}
	return ""
}

type Chain struct {
	g            *Grid
	strategyName string
	getWeaks     func(*ChainNode) []GroupItem
	getStrongs   func(node *ChainNode) []GroupItem
	closeCycle   func(head, tail GroupItem) bool
	canRemove    func(items []GroupItem) []GroupItem
}

func strChain(items []GroupItem) string {
	var b strings.Builder
	link := "="
	for _, item := range items[1 : len(items)-1] {
		fmt.Fprintf(&b, "%v%v", item, link)
		if link == "=" {
			link = "-"
		} else {
			link = "="
		}
	}
	fmt.Fprintf(&b, "%v", items[len(items)-1])
	return b.String()
}

func (c Chain) ExecStrategy3() (used bool, results []*Result) {
	usedItems := map[GroupItem]bool{}
	for _, cell := range c.g.allCells {
		if cell.solved() {
			continue
		}
		for _, cand := range cell.cands.GetList() {
			if usedItems[GroupItem{cand, [9]*Cell{cell}}] {
				continue
			}
			item := GroupItem{cand, [9]*Cell{cell}}
			node := &ChainNode{pre: nil, item: item}
			if ok, items := c.findCycle(node); ok {
				//fmt.Println(cell, cand, strLoop(items))
				removed := c.canRemove(items)
				//fmt.Println(removed)
				if len(removed) == 0 {
					continue
				}
				used = true
				if OUTPUTRESULT {
					result := new(Result)
					for _, item := range removed {
						result.addRemoved(set.NewSet(item.cand), item.getCells())
						usedItems[item] = true
					}
					result.addHelpChains(strChain(items))
					result.setStrategyName(c.strategyName)
					results = append(results, result)
				}
				if CHANGE {
					for _, item := range removed {
						removeCands(set.NewSet(item.cand), item.getCells())
					}
				}
				if !EXHAUSTIVE {
					return
				} else {
					for _, item := range removed {
						usedItems[item] = true
					}
				}
			}
		}
	}
	return
}

func (c Chain) findCycle(head *ChainNode) (used bool, items []GroupItem) {
	levelNum := 1
	curLevel := []*ChainNode{head}
	usedWeaks := map[GroupItem]bool{}
	usedStrongs := map[GroupItem]bool{}
	usedWeaks[head.item] = true
	usedStrongs[head.item] = true
	strongFlag := false
	for levelNum < 30 {
		//fmt.Println(levelNum, strongFlag)
		nextLevel := []*ChainNode{}
		for _, node := range curLevel {
			if strongFlag {
				strongs := c.getStrongs(node)
				for _, strong := range strongs {
					if usedStrongs[strong] {
						continue
					}
					if isExistedItem(node, strong) {
						continue
					}
					next := &ChainNode{node, strong}
					if levelNum > 3 && c.closeCycle(head.item, next.item) {
						//fmt.Println("close", node.cell, )
						used = true
						items = getItemsFromNode(next)
						//for n := next; n != nil; n = n.pre {
						//	items = append(items, n.item)
						//}
						//slices.Reverse(items)
						return
					}
					nextLevel = append(nextLevel, next)
					usedStrongs[strong] = true
				}
			} else {
				weaks := c.getWeaks(node)
				for _, weak := range weaks {
					if usedWeaks[weak] {
						continue
					}
					if isExistedItem(node, weak) {
						continue
					}
					next := &ChainNode{node, weak}
					nextLevel = append(nextLevel, next)
					usedWeaks[weak] = true
				}
			}
		}
		if len(nextLevel) == 0 {
			break
		}
		//for _, node := range nextLevel {
		//	fmt.Println("  ", node.item)
		//}
		//fmt.Println()
		curLevel = nextLevel
		strongFlag = !strongFlag
		levelNum++
	}

	return
}

func getItemsFromNode(node *ChainNode) (items []GroupItem) {
	for n := node; n != nil; n = n.pre {
		items = append(items, n.item)
	}
	slices.Reverse(items)
	return
}

func isExistedItem(node *ChainNode, item GroupItem) bool {
	for n := node; n != nil; n = n.pre {
		if item == n.item {
			return true
		}
	}
	return false
}

func (g *Grid) canRemoveSingle(items []GroupItem) (rmItems []GroupItem) {
	head := items[1]
	tail := items[len(items)-1]
	rmCells := g.candCellsSeenCells(head.cand, []*Cell{head.group[0], tail.group[0]})
	for _, cell := range rmCells {
		rmItems = append(rmItems, GroupItem{head.cand, [9]*Cell{cell}})
	}
	return
}

func (c Chain) ExecStrategy2() (used bool, results []*Result) {
	for _, cell := range c.g.allCells {
		if cell.solved() {
			continue
		}
		for _, cand := range cell.cands.GetList() {
			item := GroupItem{cand, [9]*Cell{cell}}
			node := &ChainNode{pre: nil, item: item}
			strongs := c.getStrongs(node)
			if len(strongs) < 2 {
				continue
			}
			if ok, items := c.findCycle2(node); ok {
				used = true
				solved := items[0].group[0]
				if OUTPUTRESULT {
					result := new(Result)
					result.setChosen(toValue(cand), solved)
					result.addHelpChains(strChain2(items))
					result.setStrategyName(c.strategyName)
					results = append(results, result)
				}
				if CHANGE {
					c.g.solveOneCell(solved.rowNum, solved.colNum, toValue(cand))
				}
				if !EXHAUSTIVE {
					return
				}
			}
		}
	}
	return
}

func (c Chain) findCycle2(head *ChainNode) (used bool, items []GroupItem) {
	levelNum := 1
	curLevel := []*ChainNode{head}
	usedWeaks := map[GroupItem]bool{}
	usedStrongs := map[GroupItem]bool{}
	usedWeaks[head.item] = true
	usedStrongs[head.item] = true
	strongFlag := true
	for levelNum < 30 {
		//fmt.Println(levelNum, strongFlag)
		nextLevel := []*ChainNode{}
		for _, node := range curLevel {
			if strongFlag {
				strongs := c.getStrongs(node)
				for _, strong := range strongs {
					if usedStrongs[strong] {
						continue
					}
					if isExistedItem(node, strong) {
						continue
					}

					next := &ChainNode{node, strong}
					nextLevel = append(nextLevel, next)
					usedStrongs[strong] = true
				}
			} else {
				weaks := c.getWeaks(node)
				for _, weak := range weaks {
					if usedWeaks[weak] {
						continue
					}
					if isExistedItem(node, weak) {
						continue
					}

					next := &ChainNode{node, weak}
					if levelNum > 3 && c.closeCycle(head.item, next.item) {
						//fmt.Println("close", node.cell, )
						used = true
						items = getItemsFromNode(next)
						//for n := next; n != nil; n = n.pre {
						//	items = append(items, n.item)
						//}
						//slices.Reverse(items)
						return
					}
					nextLevel = append(nextLevel, next)
					usedWeaks[weak] = true
				}
			}
		}
		if len(nextLevel) == 0 {
			break
		}
		//for _, node := range nextLevel {
		//	fmt.Println("  ", node.item)
		//}
		//fmt.Println()
		curLevel = nextLevel
		strongFlag = !strongFlag
		levelNum++
	}

	return
}

func strChain2(items []GroupItem) string {
	var b strings.Builder
	link := "="
	for _, item := range items {
		fmt.Fprintf(&b, "%v%v", item, link)
		if link == "=" {
			link = "-"
		} else {
			link = "="
		}
	}
	fmt.Fprintf(&b, "%v", items[0])
	return b.String()
}
